Skip to main content

How to set up autotools for 42sh ?

On this page you can find out how to get started with Autotools for 42sh.

Architecture of your 42sh

We will start by looking at how to create a proper architecture for your shell.

As an example, we will use a sample project with an architecture containing sub-folders. For each sub-folder, we will want to create the corresponding static library to then be able to produce the final binary.

Sample architecture:

42sh
├── src
│ ├── ast
│ │ ├── ast.h
│ │ ├── ast.c
│ │ └── Makefile.am
│ ├── parser
│ │ ├── parser.h
│ │ ├── parser.c
│ │ └── Makefile.am
│ ├── Makefile.am
│ └── 42sh.c
├── configure.ac
└── Makefile.am

What is Autoconf?

Autoconf is a tool for configuring the build, for example checking that our compiler has the right flags for our project, checking that the libraries are installed correctly and generating the makefiles.

Creating configure.ac

Start by creating a file called configure.ac at your project’s root directory. This file is used by autoconf to create the configure POSIX shell script that users must run before building.

The file should include at a minimum the M4 macros AC_INIT and AC_OUTPUT. You are not required to have any knowledge of the M4 language to utilize these macros and the relevant ones are documented.

To put it simply, an M4 macro is just a templated shell script.

The AC_INIT macro can include the package name, version, and email address for reporting bugs, the project URL, and, optionally, the name of the source TAR file.

AC_INIT has several parameters: the first allows you to say that you will have makefiles in sub-folders, the second allows you to deactivate the fact that the project must respect the GNU architecture which has the constraint of having different files at the root such as NEWS, Changelog, AUTHORS, README and others.

The AC_OUTPUT macro is much simpler and accepts no arguments.

The macros for generating a Makefile are as follows: AM_INIT_AUTOMAKE, which does not require any arguments, and AC_CONFIG_FILES, which takes the name you want to assign to your output file.

You need to incorporate a macro that corresponds to the specific compiler your project requires. For instance, a project coded in C would necessitate the use of AC_PROG_CC, as outlined in the Autoconf documentation.

AX_COMPILER_FLAGS is used to check if the compiler supports a given set of flags

tip

Macros prefixed with AX_ come from the package autoconf-archive. It contains a set of macros to perform additional checks.

Checkout the list of macros defined in autoconf-archive.

The AM_PROG_AR macro verifies that ar, a tool to creates archives, is present on your system and configures build options associated with archiving if it is available. The AC_PROG_RANLIB macro checks for the existence of the ranlib tool on the system.

configure.ac
# Init the 42sh project
AC_INIT([42sh], [1.0], [42sh@assistants.epita.fr])

# Setup automake
AM_INIT_AUTOMAKE([subdir-objects] [foreign])

# Pretty display of makefile rules
AM_SILENT_RULES([yes])

# Enable ar for Makefile
AM_PROG_AR

# Check if a ranlib is available
AC_PROG_RANLIB

# Check if a compiler is available for c
AC_PROG_CC

# Check if a compiler have this list of flags
AX_COMPILER_FLAGS([], [], [], [-Wall -Wextra -Werror -Wvla -pedantic -std=c99])

# List Makefile in subdirectories
AC_CONFIG_FILES([
Makefile
src/Makefile
src/ast/Makefile
src/parser/Makefile
])
AC_OUTPUT

Setting up the build environment

To create the configure script from the configure.ac file, you can use autoreconf with the --install flag. This command processes the Autoconf macros and generates the configure script. Here is how you do it:

42sh$ autoreconf --install

Now that you have your configure script, it is time to configure the build environment. This step involves detecting your system's capabilities, setting up paths, and ensuring all the necessary tools and libraries are available. To do so, simply run:

42sh$ ./configure

What is Automake?

The Makefile.am script is like a blueprint for generating Makefile.in. It is similar to how configure.ac simplifies creating complex files. Automake is the tool you will use to build the Makefile and can be customized further using autoconf.

Let's move on to the creation of the various static libraries.

Creating libast.a library

In this section, the goal is to create the Makefile.am to produce src/ast/libast.a.

Automake is based on a template system to generate makefiles. To do this, we need to define different variables to define our target.

lib_LIBRARIES is an automake variable where we define the name of the library we want to produce. The lib_ prefix is a convention to indicate that this is a library target. We are going to define a variable for all of the sources of our library: libast_a_SOURCES.

# Name of the libaries
lib_LIBRARIES = libast.a

# List of file to compile in libast.a
libast_a_SOURCES = \
ast.c \
ast.h
tip

Automake has a variable system based on convention which is the name_of_target_AM_FEATURE.

For example:

  • name_of_target_CFLAGS: Flags compiler for the target.
  • name_of_target_LDFLAGS: Linker flags for the target.

Checkout the GNU documentation

In name of target, . are replaced by _ for example libast.a becomes libast_a_AM_FEATURE.

The next part of the Makefile.am specifies compiler and preprocessor flags for building the library.

libast_a_CPPFLAGS = -I$(top_srcdir)/src

libast_a_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic

libast_a_CPPFLAGS sets the preprocessor flags. It specifies -I$(top_srcdir)/src, which is used to include $(top_srcdir)/src directory with -I option. This flag ensures that the compiler can find header files in the source and build directories.

libast_a_CFLAGS sets the compiler flags.

danger

You need to add the .h in the sources, they are important for creating the package.

The noinst_LIBRARIES variable is used to specify libraries (object code files or static libraries) that should not be installed when the make install command is run.

src/ast/Makefile.am
lib_LIBRARIES = libast.a

libast_a_SOURCES = \
ast.c \
ast.h

libast_a_CPPFLAGS = -I$(top_srcdir)/src

libast_a_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic

noinst_LIBRARIES = libast.a

Source code:

src/ast/ast.h
#ifndef AST_H
#define AST_H

void print_ast(void);

#endif /* ! AST_H */
src/ast/ast.c
#include "ast.h"

#include <stdio.h>

#include "parser/parser.h"

void print_ast(void)
{
puts("ast !!!");
print_parser();
}

Creating libparser.a library

We are now going to repeat the same process for parser.

src/parser/Makefile.am
lib_LIBRARIES = libparser.a

libparser_a_SOURCES = \
parser.c \
parser.h

libparser_a_CPPFLAGS = -I$(top_srcdir)/src

libparser_a_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic

noinst_LIBRARIES = libparser.a

Code source:

src/parser/parser.h
#ifndef PARSER_H
#define PARSER_H

void print_parser(void);

#endif /* ! PARSER_H */
src/parser/parser.c
#include "parser.h"

#include <stdio.h>

void print_parser(void)
{
puts("parser !!!");
}

Creating 42sh binary

Now that we have created the libraries, which are the dependencies of the 42sh binary, we can proceed on producing the binary by configuring the src/Makefile.am file.

SUBDIRS = ast \
parser

Here, the SUBDIRS variable is used to specify two subdirectories that contains Makfile.am files: ast and parser. Each of these subdirectories likely contains code related to abstract syntax trees and parsing, respectively.

bin_PROGRAMS = 42sh

The bin_PROGRAMS variable specifies the 42sh binary.

42sh_SOURCES = 42sh.c

42sh_CPPFLAGS = -I%D%

42sh_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic

The 42sh_SOURCES variable specifies the source file for the program . The 42sh_CPPFLAGS and 42sh_CFLAGS variables respectively define preprocessor and compiler flags.

42sh_LDADD =  \
ast/libast.a \
parser/libparser.a

The 42sh_LDADD variable lists the dependencies the main program relies on during the linking phase. In our case, it includes static libraries (libast.a and libparser.a).

danger

The order of dependencies in the link is important

src/42sh.c
#include "ast/ast.h"

int main(void)
{
print_ast();
return 0;
}
src/Makefile.am
# define the sub directories
SUBDIRS = ast \
parser

bin_PROGRAMS = 42sh

42sh_SOURCES = 42sh.c

42sh_CPPFLAGS = -I%D%

42sh_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic

42sh_LDADD = \
ast/libast.a \
parser/libparser.a
tip

To check that your build system is valid, run the make distcheck command.

make distcheck is a command used to create and test a distribution package. It consists of configuring, building and checking the software in a separate directory to ensure that the distribution package is complete and functional. This allows potential problems to be detected before the software is distributed to other systems.

How to make the target

Now that everything is ready, we are going to launch the build. If you have not already done so, you need to configure the build.

42sh$ autoreconf --force --verbose --install

42sh$ ./configure

Let's launch the build.

42sh$ make
info

You can use make -j4 to parallelize the build on 4 cpu cores.

Hooking a testsuite

The build system should allow you to run tests from it. To do this, you will need to add a Makefile.am to the tests folder. Then add the check-local rule.

tests/Makefile.am
check-local:
./testsuite.sh
danger

Don't forget to add makefile to configure.ac and to add the tests folder to Makefile.am at the root.